#!/usr/bin/perl 
#
# (C) Copyright 2010- Hewlett-Packard Development Company, L.P.
#
# $Id: instance.pm 18812 2012-06-19 07:17:25Z yi.wang4@hp.com $
#

package instance;
require Exporter;

our @ISA = qw(Exporter);
our %EXPORT_TAGS = ('all' => [qw(
    set_instance_conf_fileloc
    get_instance_conf_ref
    get_inst_config_type
    get_cluster_IP
    get_instance_id_list
    get_instance_user_list
    get_instance_id
    get_instance_sqconfig
    get_instance_usergroup
    get_instance_usertype
    get_instance_noderange
    get_instance_nodelist
    get_instance_racklist
    get_instance_floatIP_flag
    get_instance_ext_floatIP
    get_instance_int_floatIP
    get_instance_bdrname
    get_instance_system_build
    get_first_nodes
    get_cluster_name
    get_nodes_in_rack
    get_headnodes
    get_application_nodes
    get_node_type
    get_node_processor_info
    get_node_rack
    get_msas_in_rack
    get_msatypes_filename
    get_image_names
    get_categories
    get_instance_storagetype
    set_instance_sqconfig
    set_instance_conf
)]);
our @EXPORT_OK = (@{$EXPORT_TAGS{'all'}});

our $version = "instance.pm 2.00";

use 5.008000;
use strict;
use warnings;

use POSIX;
use Data::Dumper qw/Dumper/;
use File::Basename;
use File::Copy;
 
use lib "/opt/hp/platform/lib/perl";
use PlatformConfig ':Procedures';

use lib "/opt/hp/nv/lib/perl";
use config ':all';

#constants
use constant {
    CRITICAL => 2,
    MAXRACK => 100, # the max number of racks, may change later
};

use constant {
    READ => 0,
    WRITE => 1
};

#global variables
my $SQ_APPLICATION = "NEOVIEW";  # Seaquest application name is Neoview, 
                                  # as per the Platform/Cluster Team
my $IMAGEDIR = "/cm/images";

# possible return values from sub
my $GOOD = 1;
my $BAD = 0;

our $gv_config_file = "$nvpath/lib/instance.conf";
our $gv_instance_conf = undef;

our $gv_init_Platform = $BAD;

sub logerr_n_die
{
my($message) =  @_;
my $errormessage;

    $errormessage = basename($0). ": ERROR: ". $message . "\n";
    print STDERR $errormessage;
    instance_log_event($errormessage);
    exit(CRITICAL);
}

sub log_warn
{
my($message) =  @_;
my $warnmessage;
    
    $warnmessage = basename($0). ": WARNING: ". $message . "\n";
    instance_log_event($warnmessage);

}

sub  get_instance_conf_ref
{
    read_config();
    return $gv_instance_conf;
}

sub set_instance_conf_fileloc($;$)
#set the location of instance.conf
{
    $gv_config_file = shift;
    my $mode = shift;

    $mode = READ if (!$mode);

    if ($mode == READ && ! -f $gv_config_file) {
        logerr_n_die("Can't find config file $gv_config_file: $!\n");
    }

    return $GOOD;
}

sub init_platform_module 
{
    return $GOOD if ($gv_init_Platform == $GOOD);

    if (defined($gv_instance_conf->{Profile})) {
        InitConfigModule($gv_instance_conf->{Profile});
    }
    else {
        InitConfigModule();
    }
    $gv_init_Platform = $GOOD;

    return $GOOD;
}

#
# this subroutine read in the instance.conf from $gv_config_file
# and store it in $gv_instance_conf
#
sub read_config
{
    if (defined($gv_instance_conf)) { # only need to run once
        return init_platform_module();
    }

    $gv_instance_conf = {Cluster_Type   => "shared",
                         Instance_Cfgs  => {},
                      };

    open(CFG, $gv_config_file) 
        || logerr_n_die("Can't open config file $gv_config_file: $!\n");
 
    my $instance_id = "";
    my $instance = undef;
    my $user_cfg;
    my $lineno = 0;
    while (<CFG>) {

        $lineno++;
        next if (/^\s*$/);
        next if (/^\s*#/);

        chomp;
        my ($key, $val) = split /\s*:\s*/;
        $key = Trim($key);
        $val = Trim($val);

        if ($key =~ /^Date$/i) {
           $gv_instance_conf->{Date} = $val;
        }
        elsif ($key =~ /^Clustername$/i) {
            $gv_instance_conf->{Cluster_Name} = $val;
        }
        elsif ($key =~ /^Cluster\s*Alias\s*IP\s*address$/i) {
            $gv_instance_conf->{Cluster_IP} = $val;
        }
        elsif ($key =~ /^Requestor\s*Name$/i) {
            $gv_instance_conf->{Requestor_Name} = $val;
        }
        elsif ($key =~ /^Requestor\s*Phone$/i) {
            $gv_instance_conf->{Requestor_Phone} = $val;
        }
        elsif ($key =~ /^Location\s*of\s*Profile.xml$/i) {
            $gv_instance_conf->{Profile} = $val;
        }
        elsif ($key =~ /^Type\s*of\s*cluster$/i) {
            $gv_instance_conf->{Cluster_Type} = $val;
        }
        elsif ($key =~ /^Instance\s*id$/i) {
             #check for duplidation first
             logerr_n_die("Duplicated instance id in $gv_config_file, line $lineno\n")
                 if (defined($gv_instance_conf->{Instance_Cfgs}->{$val}));

            # if this is not the first instance, store the previous one
             $gv_instance_conf->{Instance_Cfgs}->{$instance_id} = $instance
                 if ($instance_id ne "");

             logerr_n_die("Invalid instance id in $gv_config_file, line $lineno\n")
                 if ($val eq "");

             $instance_id = $val;
             $instance = {SQConfig => "",
                          Float_IP_Flag => "no",
                          Users => []
                         };
             $user_cfg = undef;
        }
        elsif ($key =~ /^Sqconfig\s*(.+)$/i) {
            $instance->{SQConfig} = $val if (defined($instance));
        }
        elsif ($key =~ /^User\s*id\s*([0-9]+)\s*([^\s]+)$/i) {
             # if this is not the first user, store the previous one
             push @{$instance->{Users}}, $user_cfg if (defined($user_cfg));

             $user_cfg = {Name => $val,
                          Group => "seaquest"};
        }
        elsif ($key =~ /^User\s*id\s*([0-9]+)\s*([^\s]+)\s*password$/i) {
             $user_cfg->{Password} = $val if (defined($user_cfg));
        }
        elsif ($key =~ /^User\s*id\s*([0-9]+)\s*([^\s]+)\s*group$/i) {
             $user_cfg->{Group} = $val if (defined($user_cfg));
        }
        elsif ($key =~ /^Type\s*of\s*user\s*(.+)$/i) {
             # end of user section, store the last user information
             push @{$instance->{Users}}, $user_cfg if (defined($user_cfg));

             $instance->{User_Type} = $val if (defined($instance));
        }
        elsif ($key =~ /^System\s*Build\s*of\s*user\s*(.+)$/i) {
            $instance->{System_Build} = $val if (defined($instance));
        }
        elsif ($key =~ /^Node\s*range\s*(.+)$/i) {
            $instance->{Node_Range} = $val if (defined($instance));
        }
        elsif ($key =~ /^Storage\s*Type\s*(.+)$/i) {
            $instance->{Storage_Type} = $val if (defined($instance));
        }
        elsif ($key =~ /^Floating\s*IP\s*Flag\s*(.+)$/i) {
            $instance->{Float_IP_Flag} = $val if (defined($instance));
        }
        elsif ($key =~ /^External\s*Floating\s*IP\s*(.+)$/i) {
            $instance->{Ext_Float_IP} = $val if (defined($instance));
        }
        elsif ($key =~ /^Internal\s*Floating\s*IP\s*(.+)$/i) {
            $instance->{Int_Float_IP} = $val if (defined($instance));
        }
        elsif ($key =~ /^BDR\s*cluster\s*name\s*(.+)$/i) {
            $instance->{BDR_Name} = $val if (defined($instance));
        }
        elsif ($key =~ /^Instance\s*contact\s*phone\s*(.+)$/i) {
            $instance->{Contact_Phone} = $val if (defined($instance));
        }
        elsif ($key =~ /^Instance\s*contact\s*(.+)$/i) {
            $instance->{Contact} = $val if (defined($instance));
        }
        elsif ($key =~ /^Special\s*Information\s*or\s*restrictions\s*for\s*user\s*(.+)$/i) {
            $instance->{Comments} = $val if (defined($instance));
        }
        elsif ($key =~ /^Special\s*Comments$/i) {
            $gv_instance_conf->{Comments} = $val;
        }
        else {
            log_warn("Invalid keyword in $gv_config_file, line $lineno\n");
        }
    }

    if (defined($instance)) {
        $gv_instance_conf->{Instance_Cfgs}->{$instance_id} = $instance;
    }
    close(CFG);

    print "DEBUG:\n" . Dumper($gv_instance_conf) . "\n" if ($ENV{PEARL_DEBUG});

    return init_platform_module();
}

#
# This sub writes the instance config file $gv_config_file
# with the information from $gv_instance_conf
#
sub write_config
{
    my $tmp_conf = "/tmp/tmp_conf.$$";
    open(OFH,">$tmp_conf") || logerr_n_die("Can't open file $tmp_conf: $!\n");

    print OFH "Date: $gv_instance_conf->{Date}\n",
              "Clustername: $gv_instance_conf->{Cluster_Name}\n",
	      "Cluster Alias IP address: $gv_instance_conf->{Cluster_IP}\n",
	      "Requestor Name: $gv_instance_conf->{Requestor_Name}\n",
	      "Requestor Phone: $gv_instance_conf->{Requestor_Phone}\n",
	      "Location of Profile.xml: $gv_instance_conf->{Profile}\n",
	      "Type of cluster: $gv_instance_conf->{Cluster_Type}\n";

    my $index = 0;
    for my $inst_id (sort keys %{$gv_instance_conf->{Instance_Cfgs}}) {
        my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};
        print OFH "\n",
	          "Instance id: $inst_id\n",
	          $instance->{SQConfig} ? "Sqconfig $inst_id: $instance->{SQConfig}\n"
                                        : "Sqconfig $inst_id: \n";

        for my $user (@{$instance->{Users}}) {
            next if (! defined($user));
            $index++;
            print OFH "User id $index $inst_id: $user->{Name}\n";
            print OFH "User id $index $inst_id password: $user->{Password}\n";
        }

	print OFH "Type of user $inst_id: $instance->{User_Type}\n",
              "System Build of user $inst_id: $instance->{System_Build}\n",
              "Node range $inst_id: $instance->{Node_Range}\n",
              "Storage Type $inst_id: $instance->{Storage_Type}\n",
              "Floating IP Flag $inst_id: $instance->{Float_IP_Flag}\n";
        if ($instance->{Float_IP_Flag} eq "yes") {
	     print OFH "External Floating IP $inst_id: $instance->{Ext_Float_IP}\n",
	                "Internal Floating IP $inst_id: $instance->{Int_Float_IP}\n";
        }
	print OFH "BDR cluster name $inst_id: $instance->{BDR_Name}\n",
	 "Instance contact $inst_id: $instance->{Contact}\n",
	 "Instance contact phone $inst_id: $instance->{Contact_Phone}\n",
	 "Special Information or restrictions for user $inst_id: $instance->{Comments}\n";
    }
    close(OFH);

    copy($tmp_conf, $gv_config_file);
    if (WIFEXITED($?) && (WEXITSTATUS($?) != 0)) {
        logerr_n_die("Failed to copy $tmp_conf to $gv_config_file");
    }

    unlink $tmp_conf;
    return $GOOD;
}

# Cluster common information
sub get_inst_config_type
#get the cluster type
{
    read_config();

    return defined($gv_instance_conf->{Cluster_Type}) ? 
           $gv_instance_conf->{Cluster_Type} :  "";
}

sub get_cluster_IP
{
   read_config();

   return defined($gv_instance_conf->{Cluster_IP}) ? 
          $gv_instance_conf->{Cluster_IP} : "0.0.0.0";
}   

sub get_instance_id_list
{
    read_config();
    my @id_list = defined($gv_instance_conf->{Instance_Cfgs}) ?
           keys %{$gv_instance_conf->{Instance_Cfgs}} : ();

    return wantarray ? @id_list : join(',', @id_list);
}

# Cluster information for instance
sub get_instance_user_list(;$)
{
    my $id = shift; # instance id

    my @id_list = $id ? ($id) : keys %{$gv_instance_conf->{Instance_Cfgs}};

    my @user_list = ();
    for my $inst_id (@id_list) {
        my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};
        for my $user (@{$instance->{Users}}) {
            push @user_list, $user->{Name};
        }
    } 

    return wantarray ? @user_list : join(',', @user_list);
}

sub get_instance_id($)
#get the instance id for a specific user
{
    my $user = shift;

    read_config();

    for my $inst_id (keys %{$gv_instance_conf->{Instance_Cfgs}}) {
        my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};
        for my $user_cfg (@{$instance->{Users}}) {
            return $inst_id if ($user eq $user_cfg->{Name});
       }
    }

    return "";
}

sub get_instance_sqconfig($)
#get the location of sqconfig
{
    my $user=shift;

    my $inst_id = get_instance_id($user);

    my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};

    return defined($instance->{SQConfig}) ? $instance->{SQConfig} : "" ;
}

sub get_instance_usergroup($)
#get the default group for a specific user
{
    my $user = shift;
    read_config();

    for my $inst_id (keys %{$gv_instance_conf->{Instance_Cfgs}}) {
        my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};
        for my $user_cfg (@{$instance->{Users}}) {
            return $user_cfg->{Group} if ($user eq $user_cfg->{Name});
       }
    }

    return "seaquest"; #use seaquest as the default, may change later
}

sub get_instance_usertype($)
#get the user type for a specific user
{
    my $user=shift;

    my $inst_id = get_instance_id($user);

    my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};

    return defined($instance->{User_Type}) ? $instance->{User_Type} : "" ;
}

sub get_instance_noderange($)
#get noderange for a specific user
{
    my $user=shift;

    my $inst_id = get_instance_id($user);

    my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};

    return defined($instance->{Node_Range}) ? $instance->{Node_Range} : "" ; 
}

sub get_instance_nodelist($)
#get node list for a specific user
{
    my $user = shift;

    my $noderange = get_instance_noderange($user);

    logerr_n_die("Noderange for instance user $user undefined\n") unless ($noderange);

    my ($first, $last) = split /\-/, $noderange;

    my @nodes = ($first .. $last);

    return wantarray ? @nodes : join(',', @nodes);
}

sub get_instance_racklist($)
#get rack list for a specific user
{
    my $user = shift;

    my @nodes = get_instance_nodelist($user);

    my %rackHash = map {get_node_rack($_) => 1} @nodes;

    my @racks = keys %rackHash;

    return wantarray ? @racks : join(',', @racks);
}

sub get_instance_floatIP_flag($)
#get float ip flag for a specific user
{
    my $user=shift;

    my $inst_id = get_instance_id($user);

    my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};

    return defined($instance->{Float_IP_Flag}) ? $instance->{Float_IP_Flag} : "" ;
}

sub get_instance_ext_floatIP($)
#get external float ip for a specific user
{
    my $user=shift;

    my $inst_id = get_instance_id($user);

    my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};

    return defined($instance->{Ext_Float_IP}) ? $instance->{Ext_Float_IP} : "0.0.0.0" ;
}

sub get_instance_int_floatIP($)
#get internal float ip for a specific user
{
    my $user=shift;

    my $inst_id = get_instance_id($user);

    my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};

    return defined($instance->{Int_Float_IP}) ? $instance->{Int_Float_IP} : "0.0.0.0" ;
}

sub get_instance_bdrname($)
#get the bdr cluster name for a specific user
{
    my $user = shift;

    my $inst_id = get_instance_id($user);

    my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};

    return defined($instance->{BDR_Name}) ? $instance->{BDR_Name} : "" ;
}

sub get_instance_system_build
#get the system build for a specific user
{
    my $user = shift;

    my $inst_id = get_instance_id($user);

    my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};

    return defined($instance->{System_Build}) ? $instance->{System_Build} : "M8" ;
}

sub get_first_nodes($)
# get the first node of all racks related with a specific user
{
    my $user = shift;

    my $noderange = get_instance_noderange($user);

    return wantarray ? () : "" if ($noderange eq "");

    my ($first, $last) = split /-/, $noderange;

    return wantarray ? () : "" if ($first gt $last);

    my @first_nodes;
    for my $racknum (1 .. MAXRACK) {
        my @nodes = get_nodes_in_rack($racknum);

        last if ( @nodes == 0 );
        for my $node ($first..$last) {

            if (grep /$node/i, @nodes) {
                push @first_nodes, $nodes[0];
                last;
            }
        }
    }

    return wantarray ? @first_nodes : join(',', @first_nodes);
}

# PlatformConfig wrappers below

sub get_cluster_name
# get the cluster name
{
    read_config();

    return GetClusterName();
}

sub get_application_nodes($)
# get all nodes for a specific application
{
    my $app = shift;

    read_config();

    my $nodes = GetApplicationNodes($app);

    return wantarray ? split(/,/, $nodes) : $nodes;
}

sub get_node_type(;$)
#get the node type: RACK or BLADE
{
    my $node = shift;

    read_config();

    return GetNodeType($node);
}

sub get_nodes_in_rack($)
#get all the nodes in a specific rack, except for head nodes
{
    my $racknum = shift;

    read_config();

    my @nodes = split(/,/, GetNodesInRack($racknum));
    @nodes = grep(!/\-hn[0-9]+/i, @nodes); # filter out head nodes

    return wantarray ? @nodes : join(',', @nodes);
}

sub get_headnodes()
{
    read_config();

    my @nodes = split(/,/, GetNodesInRack(1));

    @nodes = grep(/\-hn[0-9]+/i, @nodes);

    return wantarray ? @nodes : join(',', @nodes);
}

sub get_node_processor_info($)
#get node info: cpu number, cores per cpu, total memory
{
    my $node = shift;

    read_config();

    return GetNodeProcessorInfo($node);
}

sub get_node_rack($)
#get the racknumber for a specific node
{
    my $node = shift;

    read_config();

    my @info = split(',', GetMiscInfo('NODE', $node));

    return $info[1];
}

sub get_msas_in_rack($)
#get all msas for a specific rack
{
    my $racknum = shift;

    read_config();

    my @msas;
    for my $msa (split(/,/, GetApplicationMSAs($SQ_APPLICATION)))
    {
        my @info = split(/,/, GetMiscInfo('MSA', $msa));

        push @msas, $msa if ($info[1] == $racknum);
    }

    return wantarray ? @msas : join(',', @msas);
}

sub get_msatypes_filename()
#get the msa type file name
{
    read_config();

    return GetMSATypesFileName();
}

#
# Return an array of image names under /cm/images
#
sub get_image_names
{   
    my @images;
    chomp(@images = `ls $IMAGEDIR 2>/dev/null`);

    if (grep(/^nv\-/, @images)) {
        @images = grep(/^nv\-/, @images);
    }
    else {
        @images = grep(!/default-image/, @images);
    }

    return wantarray ? @images : join(',', @images);
}

#
# Return the categories
#
sub  get_categories
{
    my @categories;

    chomp( my @images = `ls $IMAGEDIR 2>/dev/null`);

    if (grep(/^nv\-/, @images)) {  # new format
        @categories = ("neoview");
    }
    else {
        @categories = ("edge");
    }
  
    return wantarray ? @categories : join(',', @categories);
}

#
# Retrun storage type for a specific user(NRAID/RAID5)
#
sub get_instance_storagetype($)
{
    my $user = shift;
    read_config();

    my $inst_id = get_instance_id($user);

    my $instance = $gv_instance_conf->{Instance_Cfgs}->{$inst_id};

    return defined($instance->{Storage_Type}) ? $instance->{Storage_Type} : "";
}



# Setters
sub set_instance_sqconfig($$)
# set the location of sqconfig and save file back
{
    my $user = shift;
    my $file = shift;

    return 0 if (!defined($gv_instance_conf));

    my $inst_id = get_instance_id($user);

    return 0 if (!$inst_id);

    $gv_instance_conf->{Instance_Cfgs}->{$inst_id}->{SQConfig} = $file;

    print "DEBUG:\n" . Dumper($gv_instance_conf) . "\n" if ($ENV{PEARL_DEBUG});

    my $ret = write_config();

    return $ret;
}

sub set_instance_conf($)
{
    $gv_instance_conf = shift;
    my $ret = write_config();

    return $ret;
}

1;

